#!/usr/bin/env wish

#Name: organizeImagesByDate.tcl
#Author: asteriskman
#License: GNU GPL v3 http://www.gnu.org/licenses/gpl.html
#Description: Given a directory, take the .jpg files therein and move them into
#             subdirectories (creating them as necessary) based on the image
#             creation date.
#TODO:
#if remove originals is selected and the destination already exists do we delete the original or not?

#needed for reading exif data from jpgs
package require jpeg

proc log { s {level normal} } {
  .tLog insert end "$s\n" $level
  .tLog see end
  update; update idletasks
}

proc clearLog { } {
  .tLog delete 0.0 end
}

proc getConfigFile { } {
  global env
  return [file join $env(HOME) ".organizeImagesByDate.cfg"]
}

proc loadConfig { } {
  global lastPath
  global lastFormat
  global lastRemove

  set configFile [getConfigFile]

  set lastPath ""
  set lastFormat ""
  set lastRemove ""

  if {[file exists $configFile]} {
    source $configFile
  }

}

proc saveConfig { } {
  global cRemoveOriginals
  set configFile [getConfigFile]

  log "Saving config to [file nativename $configFile]\n"

  set lastPath [.ePath get]
  set lastFormat [.eDateFormat get]
  set lastRemove $cRemoveOriginals

  if {[catch {set fd [open $configFile "w"]} err]} {
    log "ERROR: Unable to save config to $configFile" error
  } else {
    puts $fd "set lastPath {$lastPath}"
    puts $fd "set lastFormat {$lastFormat}"
    puts $fd "set lastRemove {$lastRemove}"
    close $fd
  }
}

proc getDefaultDir {} {
  global env

  set defaultsList [list                                 \
    [file join $env(HOME) "My Documents" "My Pictures"]  \
    [file join $env(HOME) "My Documents"]                \
    [file join $env(HOME) "Pictures"]                    \
    $env(HOME)                                           \
    .                                                    \
  ]

  foreach d $defaultsList {
    if {[file isdirectory $d]} {
      return $d
    }
  }

  #if somehow we get here just return the current directory...it should always exist
  return .

}

proc doChooseDirectory { } {
  set result [tk_chooseDirectory -initialdir [.ePath get] -mustexist 1 -parent . -title "Choose a directory"]

  if {[string length $result] != 0} {
    .ePath delete 0 end
    .ePath insert 0 [file nativename $result]
  }
}

proc getFileDate {filename} {
  if {![::jpeg::isJPEG $filename]} {
    return -1
  }
  array set exif [::jpeg::getExif $filename]

  if {[info exists exif(DateTimeOriginal)]} {
    set stringDate $exif(DateTimeOriginal)
  } elseif {[info exists exif(DateTimeDigitized)]} {
    set stringDate $exif(DateTimeDigitized)
  } elseif {[info exists exif(DateTime)]} {
    set stringDate $exif(DateTime)
  } else {
    return -1
  }

  #stringDate will be formatted like YYYY:MM:DD HH:MM:SS
  #time is in 24 hour format
  #all fields may be spaces when the time is unknown
  if {[catch {set dateTime [clock scan $stringDate -format "%Y:%m:%d %H:%M:%S"]} fd]} {
    return -1
  }

  return $dateTime
}

proc doRun { } {
  global cRemoveOriginals

  clearLog
  log "Organizing files in [.ePath get] by [.eDateFormat get]\n"


  saveConfig

  set runDirectory [file normalize [.ePath get]]
  set organizeFormat [.eDateFormat get]


  set files [glob -nocomplain -directory $runDirectory -type f *.jpg]
  set numFiles [llength $files]
  log "Processing $numFiles files"

  set files [lsort -dictionary $files]

  set i 1
  foreach f $files {
    set date [getFileDate $f]
    set simpleFileName [file tail $f]
    set messageType normal
    if {$date != -1} {
      set simpleDirName [clock format $date -format $organizeFormat]
    } else {
      set simpleDirName "DateUnknown"
      set messageType warning
    }
    set dirName [file normalize [file join $runDirectory $simpleDirName]]
    file mkdir $dirName
    set finalName [file join $dirName $simpleFileName]
    if {![file exists $finalName]} {
      if {$cRemoveOriginals == 1} {
        file rename -- $f $finalName
      } else {
        file copy -- $f $finalName
      }
      log "($i / $numFiles) $simpleFileName => $simpleDirName" $messageType
    } else {
      log "($i / $numFiles) $simpleFileName => $simpleDirName - Exists" warning
    }
    incr i
  }
  log "Done" success
}

proc doDateFormatHelp { } {
  log "--- Formatting Help ---
The date format is specified as per the tcl clock format command.
Details of this command and all the formatting options can
be seen at http://www.tcl.tk/man/tcl8.5/TclCmd/clock.htm

The format is a set of format specifiers, which begin with a %
and other characters. Use of [file nativename /] allows for
hierarchical organization. The basic specifiers to use are:
%Y = 4 digit year
%m = 2 digit month (0 padded)
%d = 2 digit day (0 padded)

Examples for a date of September 23 2010:
%Y_%m => 2010_09
%Y[file nativename /]%m-%d => 2010[file nativename /]09-23
"
}

proc initGui {} {
  global lastPath
  global lastFormat
  global lastRemove
  global cRemoveOriginals

  label .lPath -text "Directory:"
  entry .ePath
  button .bChangeDirectory -text "Choose Directory" -command {doChooseDirectory}

  label .lDateFormat -text "Date Format:"
  entry .eDateFormat
  button .bDateFormatHelp -text "Format Help" -command {doDateFormatHelp}

  checkbutton .cRemoveOriginals -text "Remove Originals" -justify left

  button .bRun -text "Run" -command {doRun}

  text .tLog
  .tLog tag configure normal -foreground #000000 -background #FFFFFF
  .tLog tag configure warning -foreground #FF9F00 -background #FFFFFF
  .tLog tag configure error -foreground #FF0000 -background #FFFFFF
  .tLog tag configure success -foreground #00FF00 -background #FFFFFF

  set bcdWidth 100

  set nextY 0
  set height 20

  place .lPath -x 0 -y $nextY -height 20
  incr nextY $height

  place .ePath -x 0 -y $nextY -height $height -relwidth 1.0 -width -$bcdWidth
  place .bChangeDirectory -relx 1.0 -x -$bcdWidth -y $nextY -height $height -width $bcdWidth
  incr nextY $height

  place .lDateFormat -x 0 -y $nextY -height $height
  incr nextY $height

  place .eDateFormat -x 0 -y $nextY -height $height -relwidth 1.0 -width -$bcdWidth
  place .bDateFormatHelp -relx 1.0 -x -$bcdWidth -y $nextY -height $height -width $bcdWidth
  incr nextY $height

  place .cRemoveOriginals -x 0 -y $nextY -height $height
  incr nextY $height

  place .bRun -x 0 -y $nextY -height $height -relwidth 1.0
  incr nextY $height

  place .tLog -x 0 -y $nextY -relwidth 1.0 -relheight 1.0 -height -$nextY

  set windowWidth 800
  set windowHeight [expr {250 + $nextY}]
  wm geometry . "${windowWidth}x${windowHeight}"
  wm title . "Organize Images By Date"

  if {[string length $lastPath] == 0} {
    .ePath insert 0 [file nativename [getDefaultDir]]
  } else {
    .ePath insert 0 $lastPath
  }

  if {[string length $lastFormat] == 0} {
    .eDateFormat insert 0 [file nativename "%Y/%m-%d"]
  } else {
    .eDateFormat insert 0 $lastFormat
  }

  if {[string length $lastRemove] == 0} {
    set cRemoveOriginals 0
  } else {
    set cRemoveOriginals $lastRemove
  }


}

loadConfig
initGui
